/*-------------------------------------------------------------------------
 *
 * dict_xsyn.c
 *	  扩展同义词词典
 *
 * Copyright (c) 2007-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  contrib/dict_xsyn/dict_xsyn.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>

#include "commands/defrem.h"
#include "tsearch/ts_locale.h"
#include "tsearch/ts_utils.h"

PG_MODULE_MAGIC;

typedef struct
{
	char	   *key;			/* 单词 */
	char	   *value;			/* 未解析的同义词列表，包括该单词本身 */
} Syn;

typedef struct
{
	int			len;
	Syn		   *syn;

	bool		matchorig;
	bool		keeporig;
	bool		matchsynonyms;
	bool		keepsynonyms;
} DictSyn;


PG_FUNCTION_INFO_V1(dxsyn_init);
PG_FUNCTION_INFO_V1(dxsyn_lexize);

static char * fc_find_word(char *fc_in, char **fc_end)
{
	char	   *fc_start;

	*fc_end = NULL;
	while (*fc_in && t_isspace(fc_in))
		fc_in += pg_mblen(fc_in);

	if (!*fc_in || *fc_in == '#')
		return NULL;
	fc_start = fc_in;

	while (*fc_in && !t_isspace(fc_in))
		fc_in += pg_mblen(fc_in);

	*fc_end = fc_in;

	return fc_start;
}

static int fc_compare_syn(const void *fc_a, const void *fc_b)
{
	return strcmp(((const Syn *) fc_a)->key, ((const Syn *) fc_b)->key);
}

static void fc_read_dictionary(DictSyn *fc_d, const char *fc_filename)
{
	char	   *fc_real_filename = get_tsearch_config_filename(fc_filename, "rules");
	tsearch_readline_state fc_trst;
	char	   *fc_line;
	int			fc_cur = 0;

	if (!tsearch_readline_begin(&fc_trst, fc_real_filename))
		ereport(ERROR,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("could not open synonym file \"%s\": %m",
						fc_real_filename)));

	while ((fc_line = tsearch_readline(&fc_trst)) != NULL)
	{
		char	   *fc_value;
		char	   *fc_key;
		char	   *fc_pos;
		char	   *fc_end;

		if (*fc_line == '\0')
			continue;

		fc_value = lowerstr(fc_line);
		pfree(fc_line);

		fc_pos = fc_value;
		while ((fc_key = fc_find_word(fc_pos, &fc_end)) != NULL)
		{
			/* 如果完整则扩大同义词结构 */
			if (fc_cur == fc_d->len)
			{
				fc_d->len = (fc_d->len > 0) ? 2 * fc_d->len : 16;
				if (fc_d->syn)
					fc_d->syn = (Syn *) repalloc(fc_d->syn, sizeof(Syn) * fc_d->len);
				else
					fc_d->syn = (Syn *) palloc(sizeof(Syn) * fc_d->len);
			}

			/* 如果我们将匹配它，则只保存第一个词 */
			if (fc_pos != fc_value || fc_d->matchorig)
			{
				fc_d->syn[fc_cur].key = pnstrdup(fc_key, fc_end - fc_key);
				fc_d->syn[fc_cur].value = pstrdup(fc_value);

				fc_cur++;
			}

			fc_pos = fc_end;

			/* 如果我们不会匹配它们，就不要费心扫描同义词 */
			if (!fc_d->matchsynonyms)
				break;
		}

		pfree(fc_value);
	}

	tsearch_readline_end(&fc_trst);

	fc_d->len = fc_cur;
	if (fc_cur > 1)
		qsort(fc_d->syn, fc_d->len, sizeof(Syn), fc_compare_syn);

	pfree(fc_real_filename);
}

Datum dxsyn_init(PG_FUNCTION_ARGS)
{
	List	   *fc_dictoptions = (List *) PG_GETARG_POINTER(0);
	DictSyn    *fc_d;
	ListCell   *fc_l;
	char	   *fc_filename = NULL;

	fc_d = (DictSyn *) palloc0(sizeof(DictSyn));
	fc_d->len = 0;
	fc_d->syn = NULL;
	fc_d->matchorig = true;
	fc_d->keeporig = true;
	fc_d->matchsynonyms = false;
	fc_d->keepsynonyms = true;

	foreach(fc_l, fc_dictoptions)
	{
		DefElem    *fc_defel = (DefElem *) lfirst(fc_l);

		if (strcmp(fc_defel->defname, "matchorig") == 0)
		{
			fc_d->matchorig = defGetBoolean(fc_defel);
		}
		else if (strcmp(fc_defel->defname, "keeporig") == 0)
		{
			fc_d->keeporig = defGetBoolean(fc_defel);
		}
		else if (strcmp(fc_defel->defname, "matchsynonyms") == 0)
		{
			fc_d->matchsynonyms = defGetBoolean(fc_defel);
		}
		else if (strcmp(fc_defel->defname, "keepsynonyms") == 0)
		{
			fc_d->keepsynonyms = defGetBoolean(fc_defel);
		}
		else if (strcmp(fc_defel->defname, "rules") == 0)
		{
			/* 我们在解析所有选项之前无法阅读规则！ */
			fc_filename = defGetString(fc_defel);
		}
		else
		{
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("unrecognized xsyn parameter: \"%s\"",
							fc_defel->defname)));
		}
	}

	if (fc_filename)
		fc_read_dictionary(fc_d, fc_filename);

	PG_RETURN_POINTER(fc_d);
}

Datum dxsyn_lexize(PG_FUNCTION_ARGS)
{
	DictSyn    *fc_d = (DictSyn *) PG_GETARG_POINTER(0);
	char	   *fc_in = (char *) PG_GETARG_POINTER(1);
	int			fc_length = PG_GETARG_INT32(2);
	Syn			fc_word;
	Syn		   *fc_found;
	TSLexeme   *fc_res = NULL;

	if (!fc_length || fc_d->len == 0)
		PG_RETURN_POINTER(NULL);

	/* 创建搜索模式 */
	{
		char	   *fc_temp = pnstrdup(fc_in, fc_length);

		fc_word.key = lowerstr(fc_temp);
		pfree(fc_temp);
		fc_word.value = NULL;
	}

	/* 查找匹配的同义词 */
	fc_found = (Syn *) bsearch(&fc_word, fc_d->syn, fc_d->len, sizeof(Syn), fc_compare_syn);
	pfree(fc_word.key);

	if (!fc_found)
		PG_RETURN_POINTER(NULL);

	/* 解析同义词字符串并返回单词数组 */
	{
		char	   *fc_value = fc_found->value;
		char	   *fc_syn;
		char	   *fc_pos;
		char	   *fc_end;
		int			fc_nsyns = 0;

		fc_res = palloc(sizeof(TSLexeme));

		fc_pos = fc_value;
		while ((fc_syn = fc_find_word(fc_pos, &fc_end)) != NULL)
		{
			fc_res = repalloc(fc_res, sizeof(TSLexeme) * (fc_nsyns + 2));

			/* 只有在keeporig=true时，第一个词才会输出 */
			if (fc_pos != fc_value || fc_d->keeporig)
			{
				fc_res[fc_nsyns].lexeme = pnstrdup(fc_syn, fc_end - fc_syn);
				fc_res[fc_nsyns].nvariant = 0;
				fc_res[fc_nsyns].flags = 0;
				fc_nsyns++;
			}

			fc_pos = fc_end;

			/* 如果我们不输出同义词，则停止 */
			if (!fc_d->keepsynonyms)
				break;
		}
		fc_res[fc_nsyns].lexeme = NULL;
	}

	PG_RETURN_POINTER(fc_res);
}
